#include "xffmpeg.h"

static double r2d(AVRational r)
{
    return r.num == 0 || r.den == 0 ? 0. : (double)r.num / (double)r.den;
}

XFFmpeg::XFFmpeg() : m_ic(NULL),
    m_totalMs(0),
    m_mutex(),
    m_videoStream(0),
    m_audioStream(1),
    m_cCtx(NULL),
    m_yuv(NULL),
    m_aCtx(NULL)
{
    av_register_all();
    memset(m_szErrorbuf, 0, sizeof(m_szErrorbuf));
}

XFFmpeg::~XFFmpeg()
{
    if(m_yuv == NULL)
        av_frame_free(&m_yuv);
}

int XFFmpeg::Open(const char* path)
{
    Close();
    m_mutex.lock();
    int ret = avformat_open_input(&m_ic, path, 0, 0);
    if(ret != 0)
    {
        av_strerror(ret, m_szErrorbuf, sizeof(m_szErrorbuf));
        m_mutex.unlock();
        return 0;
    }
    m_totalMs = m_ic->duration / AV_TIME_BASE;    //总秒数

    //打开解码器
    int videoStream = 0;
    AVCodecContext *videoCtx = NULL;
    for(int i = 0; i < m_ic->nb_streams; i++)
    {
        AVCodecContext *enc = m_ic->streams[i]->codec;
        if(enc->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            m_videoStream = i;
            m_fps = r2d(m_ic->streams[i]->avg_frame_rate);
            AVCodec *decodec = avcodec_find_decoder(enc->codec_id);
            if(!decodec)
            {
                strcpy_s(m_szErrorbuf, sizeof(m_szErrorbuf), "video decode not find\n");
                avformat_close_input(&m_ic);
                m_mutex.unlock();
                return 0;
            }
            int err = avcodec_open2(enc, decodec, NULL);
            if(err != 0)
            {
                av_strerror(err, m_szErrorbuf, sizeof(m_szErrorbuf));
                avformat_close_input(&m_ic);
                m_mutex.unlock();
                return 0;
            }
            videoCtx = enc;
        }
        else if(enc->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            m_audioStream = i;
            AVCodec *codec = avcodec_find_decoder(enc->codec_id);
            if(avcodec_open2(enc, codec, NULL) < 0)
            {
                m_mutex.unlock();
                return false;
            }
            m_iSampleRate = enc->sample_rate;
            m_iChannel = enc->channels;
            switch (enc->sample_fmt) {
            case AV_SAMPLE_FMT_S16:
                m_iSampleSize = 16;
                break;
            case AV_SAMPLE_FMT_S32:
                m_iSampleSize = 32;
                break;
            case AV_SAMPLE_FMT_FLTP:
                m_iSampleSize = 32;
                break;
            default:
                break;
            }
            printf("audio sample rate:%d sample size:%d channle %d\n", m_iSampleRate, m_iSampleSize, m_iChannel);
        }

    }

    m_mutex.unlock();
    return m_totalMs;
}

void XFFmpeg::Close()
{
    m_mutex.lock();
    if(m_ic)
    {
        avformat_close_input(&m_ic);
    }

    if(m_cCtx)
    {
        sws_freeContext(m_cCtx);
        m_cCtx = NULL;
    }

    if(m_aCtx)
    {
        swr_free(&m_aCtx);
        m_aCtx = NULL;
    }
    m_mutex.unlock();

}

AVPacket XFFmpeg::Read()
{
    AVPacket pkt;
    memset(&pkt, 0, sizeof(pkt));
    m_mutex.lock();
    if(!m_ic)
    {
        m_mutex.unlock();
        return pkt;
    }
    int ret = av_read_frame(m_ic, &pkt);
    if(ret != 0)
    {
        av_strerror(ret, m_szErrorbuf, sizeof(m_szErrorbuf));
    }
    m_mutex.unlock();
    return pkt;
}

int XFFmpeg::GetPTS(const AVPacket* pkt)
{
    m_mutex.lock();
    if(!m_ic)
    {
        m_mutex.unlock();
        return -1;
    }
    int pts = (pkt->pts * r2d(m_ic->streams[pkt->stream_index]->time_base)) * 1000;
    m_mutex.unlock();
    return pts;
}

int XFFmpeg::Decode(const AVPacket &pkt)
{
    m_mutex.lock();
    if(!m_ic)
    {
        m_mutex.unlock();
        return NULL;
    }
    if(m_yuv == NULL)
    {
        m_yuv = av_frame_alloc();
    }
    if(m_pcm == NULL)
    {
        m_pcm = av_frame_alloc();
    }
    AVFrame* frame = m_yuv;
    if(pkt.stream_index == m_audioStream)
    {
        frame = m_pcm;
    }

    int ret = avcodec_send_packet(m_ic->streams[pkt.stream_index]->codec, &pkt);
    if(ret != 0)
    {
        m_mutex.unlock();
        return NULL;
    }
    ret = avcodec_receive_frame(m_ic->streams[pkt.stream_index]->codec, frame);
    if(ret != 0)
    {
        m_mutex.unlock();
        return NULL;
    }
    m_mutex.unlock();
    int p = (frame->pts * r2d(m_ic->streams[pkt.stream_index]->time_base)) * 1000;
    if(pkt.stream_index == m_audioStream)
        m_pts = p;
    return p;
}

bool XFFmpeg::ToRGB(char* out, int outwidth, int outheight)
{
    m_mutex.lock();

    if(!m_ic || m_yuv == NULL)
    {
        m_mutex.unlock();
        return false;
    }

    AVCodecContext *videoCtx = m_ic->streams[m_videoStream]->codec;
    //打开转码器
    m_cCtx = sws_getCachedContext(m_cCtx, videoCtx->width, videoCtx->height,
                         videoCtx->pix_fmt, outwidth, outheight, AV_PIX_FMT_BGRA,
                         SWS_BICUBIC, NULL, NULL, NULL);
    if(!m_cCtx)
    {
        m_mutex.unlock();
        strcpy(m_szErrorbuf, "sws_getCachedContext failed");
        return false;
    }

    //转码
    uint8_t *data[AV_NUM_DATA_POINTERS] = {0};
    data[0] = (uint8_t *)out;
    int linesize[AV_NUM_DATA_POINTERS] = {0};
    linesize[0] = outwidth * 4;
    sws_scale(m_cCtx, m_yuv->data, m_yuv->linesize, 0, videoCtx->height,
              data, linesize);

    m_mutex.unlock();
    return true;
}

int XFFmpeg::ToPCM(char *out)
{
    m_mutex.lock();

    if(!m_ic || !m_pcm || !out)
    {
        m_mutex.unlock();
        return 0;
    }

    AVCodecContext *ctx = m_ic->streams[m_audioStream]->codec;
    if(m_aCtx == NULL)
    {
        m_aCtx = swr_alloc();
        swr_alloc_set_opts(m_aCtx, ctx->channel_layout, AV_SAMPLE_FMT_S16,
                           ctx->sample_rate, ctx->channels, ctx->sample_fmt,
                           ctx->sample_rate, 0, 0);
        swr_init(m_aCtx);
    }

    uint8_t* data[1] = {0};
    data[0] = (uint8_t*)out;
    int len = swr_convert(m_aCtx, data, 10000, (const uint8_t**)m_pcm->data, m_pcm->nb_samples);
    if(len <= 0)
    {
        m_mutex.unlock();
        return 0;
    }

    int outsize = av_samples_get_buffer_size(NULL, ctx->channels,
                                             m_pcm->nb_samples, AV_SAMPLE_FMT_S16, 0);
    m_mutex.unlock();
    return outsize;
}

bool XFFmpeg::Seek(float pos)
{
    m_mutex.lock();
    if(!m_ic)
    {
        m_mutex.unlock();
        return false;
    }
    int64_t stamp = 0;
    stamp = pos * m_ic->streams[m_videoStream]->duration;
    int re = av_seek_frame(m_ic, m_videoStream, stamp, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);
    avcodec_flush_buffers(m_ic->streams[m_videoStream]->codec);
    m_pts = (stamp * r2d(m_ic->streams[m_videoStream]->time_base)) * 1000;
    m_mutex.unlock();
    if(re > 0)
        return true;
    return false;
}

std::string XFFmpeg::GetError()
{
    m_mutex.lock();
    std::string ret = m_szErrorbuf;
    m_mutex.unlock();
    return ret;
}
